package org.bspv.pharma.dao.impl;

import java.util.List;

import org.bspv.pharma.dao.PlaceDAO;
import org.bspv.pharma.model.Place;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Restrictions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

/**
 * 
 * @author guillaume
 * 
 */
@Repository(value = "placeDAO")
public class PlaceDAOImpl extends AbstractHibernateDAO<Place, Long> implements PlaceDAO {

    /**
     * Constructeur.
     * 
     * @param sessionFactory
     */
    @Autowired
    public PlaceDAOImpl(SessionFactory sessionFactory) {
        setSessionFactory(sessionFactory);
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    public List<Place> findAllValidChildren(Place parent) {
        DetachedCriteria crit = DetachedCriteria.forClass(getManagedType());
        crit.add(Restrictions.gt("lowerMarkup", parent.getLowerMarkup()));
        crit.add(Restrictions.lt("upperMarkup", parent.getUpperMarkup()));
        crit.add(Restrictions.isNull("obsolete"));
        return getHibernateTemplate().findByCriteria(crit);
    }

    /**
     * Efface l'emplacement en paramètre et ses sous-emplacements.
     * 
     * @param e
     *            Emplacement à effacer
     */
    public void delete(Place e) {
        // Calcul du décalage (représentation intervallaire)
        int delta = e.getUpperMarkup() - e.getLowerMarkup() + 1;
        int upperMarkup = e.getUpperMarkup();
        int lowerMarkup = e.getLowerMarkup();

        Session session = getSessionFactory().openSession();
        Transaction tx = session.beginTransaction();

        // Requete de suppression des noeuds (noeud de base et sous-noeuds)
        String hqlDelete = "delete Place p where p.upperMarkup <= :upperMarkup and p.lowerMarkup >= :lowerMarkup";
        // Execution de la requete
        int deletedEntities = session.createQuery(hqlDelete).setInteger("upperMarkup", upperMarkup)
                .setInteger("lowerMarkup", lowerMarkup).executeUpdate();
        logger.info("Suppression de " + deletedEntities + "emplacements");

        // Requetes de mise à jour des bornes
        String hqlUpperMarkup = "update Place p set p.upperMarkup = p.upperMarkup - :delta where p.lowerMarkup > :eUpperMarkup or p.upperMarkup > :eUpperMarkup";
        String hqlLowerMarkup = "update Place p set p.lowerMarkup = p.lowerMarkup - :delta where p.lowerMarkup > :eUpperMarkup";
        // Execution des requetes
        int updatedEntitiesUpper = session.createQuery(hqlUpperMarkup).setInteger("delta", delta)
                .setInteger("eUpperMarkup", e.getUpperMarkup()).executeUpdate();
        int updatedEntitiesLower = session.createQuery(hqlLowerMarkup).setInteger("delta", delta)
                .setInteger("eUpperMarkup", e.getUpperMarkup()).executeUpdate();
        logger.info("Modification de " + updatedEntitiesUpper + "emplacements (upper)");
        logger.info("Modification de " + updatedEntitiesLower + "emplacements (lower)");

        tx.commit();
        session.close();
    }

    /**
     * Méthode d'ajout d'un emplacement.(feuille de l'arbre)
     * (Ajout à droite)
     * @param e
     *            L'emplacement à ajouter.
     * @return L'emplacement ajouté.
     */
    public Long add(Place place) {
        // Récupération du parent s'il existe
        Place parent = place.getParent();
        // Calcul des bornes
        if (parent == null) {
            //Ajout à la racine
            //Récupération du nombre d'emplacement pour déterminer les bornes
            int nbEmplacement = this.getElementCount();
            place.setLowerMarkup(2 * nbEmplacement + 1);
            place.setUpperMarkup(place.getLowerMarkup() + 1);
        } else {
            //Le parent n'est pas nul
            place.setLowerMarkup(parent.getUpperMarkup());
            place.setUpperMarkup(place.getLowerMarkup() + 1);
        }
        Session session = getSessionFactory().openSession();
        Transaction tx = session.beginTransaction();
        //Décalage des bornes pour les noeuds concernés
        int delta = 2;
        // Requetes de mise à jour des bornes
        String hqlUpperMarkup = "update Place p set p.upperMarkup = p.upperMarkup + :delta where p.lowerMarkup > :eUpperMarkup or p.upperMarkup > :eUpperMarkup";
        String hqlLowerMarkup = "update Place p set p.lowerMarkup = p.lowerMarkup + :delta where p.lowerMarkup > :eUpperMarkup";
        // Execution des requetes
        int updatedEntitiesUpper = session.createQuery(hqlUpperMarkup).setInteger("delta", delta)
                .setInteger("eUpperMarkup", place.getUpperMarkup()).executeUpdate();
        int updatedEntitiesLower = session.createQuery(hqlLowerMarkup).setInteger("delta", delta)
                .setInteger("eUpperMarkup", place.getUpperMarkup()).executeUpdate();
        logger.info("Modification de " + updatedEntitiesUpper + "emplacements (upper)");
        logger.info("Modification de " + updatedEntitiesLower + "emplacements (lower)");
        tx.commit();
        session.close();
        //Enregistrement du nouveau noeud
        return this.save(place);
    }

    
    /**
     * Mise à jour d'un emplacement. 
     * precondition : le nouveau parent existe en bdd (ou est nul)
     * 
     * @param newPlace
     *            Emplacement à mettre à jour
     * @return Emplacement mis à jour.
     */
    public Place update(Place newPlace) {
        // Récupération de l'ancien emplacement
        Place oldPlace = this.findById(newPlace.getId());
        Place oldParent = oldPlace.getParent();
        Place newParent = newPlace.getParent();
        int alpha = 0;//Borne supérieure du futur parent (ou max+1)
        int beta = 0;// Borne supérieure de l'ancien parent (ou max+1)
        int delta = 0;//Décalage entre la borne supérieure du futur parent et la borne inférieure de l'emplacement.
        int gamma = newPlace.getUpperMarkup() - newPlace.getLowerMarkup(); //"taille" du sous arbre
        
        boolean differentParent = false;
        
        // Changement de parent ?
        if ((newParent != null && oldParent != null && !newParent.equals(oldParent))) {
            differentParent = true;
            alpha = newParent.getUpperMarkup();
            beta = oldParent.getUpperMarkup();
        } else if (newParent == null && oldParent != null) {//L'emplacement remonte à la racine
            differentParent = true;
            alpha = (this.getElementCount()*2) + 1;
            beta = oldParent.getUpperMarkup();
        } else if (newParent != null && oldParent == null) {
            differentParent = true;
            alpha = newParent.getUpperMarkup();
            beta = (this.getElementCount()*2) + 1;
        }
        
        // Enregistrement du nouvel emplacement sans mise à jour de la numérotation
        this.saveOrUpdate(newPlace);
        
        // Changement de parent ! (renumérotation)
        if (differentParent) {
            delta = alpha - newPlace.getLowerMarkup();
            
            Session session = getSessionFactory().openSession();
            Transaction tx = session.beginTransaction();
            String hqlUpperMarkup = "";
            String hqlLowerMarkup = "";
            String hqlUpperLowerMarkup = "";
            int updatedEntitiesUpper = 0;
            int updatedEntitiesLower = 0;
            int updatedEntitiesUpperLower = 0;
            Integer eLowerMarkup = newPlace.getLowerMarkup();
            Integer eUpperMarkup = newPlace.getUpperMarkup();
            //a) Décalage de toutes les bornes supérieures ou égales à la borne supérieure du futur parent de (gamma+1)
            hqlUpperMarkup = "update Place p set p.upperMarkup = p.upperMarkup + :gamma + 1 where p.upperMarkup >= :alpha";
            hqlLowerMarkup = "update Place p set p.lowerMarkup = p.lowerMarkup + :gamma + 1 where p.lowerMarkup >= :alpha";
            updatedEntitiesUpper = session.createQuery(hqlUpperMarkup)
                .setInteger("gamma", gamma)
                .setInteger("alpha", alpha)
                .executeUpdate();
            updatedEntitiesLower = session.createQuery(hqlLowerMarkup)
                .setInteger("gamma", gamma)
                .setInteger("alpha", alpha)
                .executeUpdate();
            logger.info("Modification des bornes supérieures ou égales à la borne sup du futur parent (" + updatedEntitiesUpper + " bornes sup)");
            logger.info("Modification des bornes supérieures ou égales à la borne sup du futur parent (" + updatedEntitiesLower + " bornes inf)");
            //b) Ajout de delta sur toutes les bornes du sous arbre à mettre à jour
            hqlUpperLowerMarkup = "update Place p set p.upperMarkup = p.upperMarkup + :delta, p.lowerMarkup = p.lowerMarkup + :delta  where p.lowerMarkup >= :eLowerMarkup and p.upperMarkup <= :eUpperMarkup";
            //hqlLowerMarkup = "update Place p set p.lowerMarkup = p.lowerMarkup + :delta where p.lowerMarkup >= :eLowerMarkup and p.upperMarkup <= :eUpperMarkup";
            updatedEntitiesUpperLower = session.createQuery(hqlUpperLowerMarkup)
                .setInteger("delta", delta)
                .setInteger("eUpperMarkup", eUpperMarkup)
                .setInteger("eLowerMarkup", eLowerMarkup)
                .executeUpdate();
            logger.info("Modification des bornes supérieures et inférieures du sous-arbre (" + updatedEntitiesUpperLower + " bornes)");
            //c) Decalage de toutes les bornes supérieures ou égales à la borne supérieure de l'ancien parent de -(gamma+1)
            hqlUpperMarkup = "update Place p set p.upperMarkup = p.upperMarkup - (:gamma + 1) where p.upperMarkup >= :oldParentUpperMarkup";
            hqlLowerMarkup = "update Place p set p.lowerMarkup = p.lowerMarkup - (:gamma + 1) where p.lowerMarkup >= :oldParentUpperMarkup";
            updatedEntitiesUpper = session.createQuery(hqlUpperMarkup)
                .setInteger("gamma", gamma)
                .setInteger("oldParentUpperMarkup", beta)
                .executeUpdate();
            updatedEntitiesLower = session.createQuery(hqlLowerMarkup)
                .setInteger("gamma", gamma)
                .setInteger("oldParentUpperMarkup", beta)
                .executeUpdate();
            logger.info("Modification des bornes supérieures ou égales à la borne sup de l'ancien parent (" + updatedEntitiesUpper + " bornes sup)");
            logger.info("Modification des bornes supérieures ou égales à la borne sup de l'ancien parent (" + updatedEntitiesLower + " bornes inf)");
            tx.commit();
            session.close();
        }
        //Rechargement de l'entité
        return this.findUniqueResultBy("code", newPlace.getCode());
    }

}
